/*
 * Copyright (C) 2015 Karumi.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.karumi.dexter;

import com.karumi.dexter.listener.DexterError;
import com.karumi.dexter.listener.PermissionDeniedResponse;
import com.karumi.dexter.listener.PermissionGrantedResponse;
import com.karumi.dexter.listener.PermissionRequest;
import com.karumi.dexter.listener.multi.BaseMultiplePermissionsListener;
import com.karumi.dexter.listener.multi.MultiplePermissionsListener;
import com.karumi.dexter.listener.single.PermissionListener;
import com.karumi.dexter.uitool.DexterLogUtil;

import ohos.aafwk.ability.Ability;
import ohos.app.Context;
import ohos.bundle.IBundleManager;

import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Inner implementation of a dexter instance holding the state of the permissions request
 */
final class DexterInstance {
    private static final int PERMISSIONS_REQUEST_CODE = 42;
    private static MultiplePermissionsListener empty_listener;

    private WeakReference<Ability> abilityContext;
    private final HarmonyPermissionService harmonyPermissionService;
    private final Collection<String> pendingPermissions;
    private final MultiplePermissionsReport multiplePermissionsReport;
    private final AtomicBoolean isRequestingPermission;
    private final AtomicBoolean rationaleAccepted;
    private final AtomicBoolean isShowingNativeDialog;
    private static final Object PENDINGPERMISSIONSMUTEX = new Object();
    private AbilityLifeObserver abilityLifeObserver;

    private Ability ability;
    private MultiplePermissionsListener listener;

    DexterInstance(Context context, HarmonyPermissionService harmonyPermissionService) {
        this.harmonyPermissionService = harmonyPermissionService;
        this.pendingPermissions = new TreeSet<>();
        this.multiplePermissionsReport = new MultiplePermissionsReport();
        this.isRequestingPermission = new AtomicBoolean();
        this.rationaleAccepted = new AtomicBoolean();
        this.isShowingNativeDialog = new AtomicBoolean();
        empty_listener = new BaseMultiplePermissionsListener();
        listener = empty_listener;
        setContext(context);

        DexterLogUtil.info("dexterLogUtil", "4");
    }

    void setContext(Context context) {
        Ability mAbility = DexterAbilityContextUtils.getAbility(context);
        this.abilityContext = new WeakReference<>(mAbility);
    }

    /**
     * Checks the state of a specific permission reporting it when ready to the listener.
     *
     * @param listener   The class that will be reported when the state of the permission is ready
     * @param permission One of the values found in {@link IBundleManager}
     * @param thread     thread the Listener methods will be called on
     */
    void checkPermission(PermissionListener listener, String permission, Thread thread) {
        checkSinglePermission(listener, permission, thread);
    }

    /**
     * Checks the state of a collection of permissions reporting their state to the listener when all
     * of them are resolved
     *
     * @param listener    The class that will be reported when the state of all the permissions is ready
     * @param permissions Array of values found in {@link IBundleManager}
     * @param thread      thread the Listener methods will be called on
     */
    void checkPermissions(MultiplePermissionsListener listener, Collection<String> permissions, Thread thread) {
        checkMultiplePermissions(listener, permissions, thread);
    }

    /**
     * Method called whenever the inner activity has been created or restarted and is ready to be
     * used.
     *
     * @param ability 上下文
     */
    void onActivityReady(Ability ability) {
        this.ability = ability;

        DexterLogUtil.info("dexterLogUtil", "22");
        PermissionStates permissionStates = null;
        synchronized (PENDINGPERMISSIONSMUTEX) {
            if (ability != null) {
                DexterLogUtil.info("dexterLogUtil", "23");
                permissionStates = getPermissionStates(pendingPermissions);
            }
        }

        if (permissionStates != null) {
            DexterLogUtil.info("dexterLogUtil", "25");
            DexterLogUtil.info("dexterLogUtil", "25" + permissionStates.getDeniedPermissions().toString());
            handleDeniedPermissions(permissionStates.getDeniedPermissions());
            DexterLogUtil.info("dexterLogUtil", "26");
            updatePermissionsAsDenied(permissionStates.getImpossibleToGrantPermissions());
            updatePermissionsAsGranted(permissionStates.getGrantedPermissions());
            DexterLogUtil.info("dexterLogUtil", "27");
        }
    }

    /**
     * 同步ability的回调方法
     *
     * @param requestCode requestCode
     * @param permissions permissions
     * @param grantResults grantResults
     */
    void onRequestPermissionResult(int requestCode, String[] permissions, int[] grantResults) {
        Collection<String> grantedPermissions = new LinkedList<>();
        Collection<String> deniedPermissions = new LinkedList<>();

        for (int i = 0; i < permissions.length; i++) {
            String permission = permissions[i];
            switch (grantResults[i]) {
                case IBundleManager.PERMISSION_DENIED:
                    deniedPermissions.add(permission);
                    break;
                case IBundleManager.PERMISSION_GRANTED:
                    grantedPermissions.add(permission);
                    break;
                default:
            }
        }
        DexterLogUtil.info(
                "dexterLogUtil",
                "18-3-1:grantedPermissions"
                        + grantedPermissions.toString()
                        + "-deniedPermissions:"
                        + deniedPermissions.toString());
        Dexter.onPermissionsRequested(grantedPermissions, deniedPermissions);
        onActivityDestroyed(ability);
    }

    /**
     * Method called whenever the inner activity has been destroyed.
     *
     * @param oldAbility the DexterActivity that was destroyed
     */
    void onActivityDestroyed(Ability oldAbility) {
        if (ability == oldAbility) {
            DexterLogUtil.info("dexterLogUtil", "ability===oldAbility");
            ability = null;
            isRequestingPermission.set(false);
            rationaleAccepted.set(false);
            isShowingNativeDialog.set(false);
            listener = empty_listener;
        }
    }

    /**
     * Method called whenever the permissions has been granted by the user
     *
     * @param permissions 权限集合
     */
    void onPermissionRequestGranted(Collection<String> permissions) {
        updatePermissionsAsGranted(permissions);
    }

    /**
     * Method called whenever the permissions has been denied by the user
     *
     * @param permissions 集合
     */
    void onPermissionRequestDenied(Collection<String> permissions) {
        updatePermissionsAsDenied(permissions);
    }

    /**
     * Method called when the user has been informed with a rationale and agrees to continue
     * with the permission request process
     */
    void onContinuePermissionRequest() {
        rationaleAccepted.set(true);
        requestPermissionsToSystem(pendingPermissions);
    }

    /**
     * Method called when the user has been informed with a rationale and decides to cancel
     * the permission request process
     */
    void onCancelPermissionRequest() {
        rationaleAccepted.set(false);
        updatePermissionsAsDenied(pendingPermissions);
    }

    /**
     * Starts the native request permissions process
     *
     * @param permissions 权限集合
     */
    private void requestPermissionsToSystem(Collection<String> permissions) {
        if (!isShowingNativeDialog.get()) {
            DexterLogUtil.info("dexterLogUtil", "25-3-1");
            harmonyPermissionService.requestPermissions(
                    ability, permissions.toArray(new String[0]), PERMISSIONS_REQUEST_CODE);
        }
        isShowingNativeDialog.set(true);
        DexterLogUtil.info("dexterLogUtil", "25-3-2");
    }

    private PermissionStates getPermissionStates(Collection<String> pendingPermissions) {
        PermissionStates permissionStates = new PermissionStates();
        DexterLogUtil.info("dexterLogUtil", "24");
        for (String permission : pendingPermissions) {
            int permissionState = checkSelfPermission(ability, permission);

            switch (permissionState) {
                case IBundleManager.PERMISSION_DENIED:
                    permissionStates.addDeniedPermission(permission);
                    break;
                case IBundleManager.PERMISSION_GRANTED:
                default:
                    permissionStates.addGrantedPermission(permission);
                    break;
            }
        }

        return permissionStates;
    }

    /**
     * Workaround for RuntimeException of Parcel#readException.
     *
     * @param activity   a
     * @param permission permission
     * @return int
     */
    private int checkSelfPermission(Ability activity, String permission) {
        try {
            return harmonyPermissionService.checkSelfPermission(activity, permission);
        } catch (RuntimeException ignored) {
            return IBundleManager.PERMISSION_DENIED;
        }
    }

    private void startTransparentActivityIfNeeded(String bundleName) {
        Ability abiy = this.abilityContext.get();
        if (abiy == null) {
            return;
        }
        onActivityReady(abiy);
    }

    private void handleDeniedPermissions(Collection<String> permissions) {
        if (permissions.isEmpty()) {
            return;
        }
        List<PermissionRequest> shouldShowRequestRationalePermissions = new LinkedList<>();

        for (String permission : permissions) {
            if (harmonyPermissionService.shouldShowRequestPermissionRationale(ability, permission)) {
                shouldShowRequestRationalePermissions.add(new PermissionRequest(permission));
            }
        }
        if (shouldShowRequestRationalePermissions.isEmpty()) {
            requestPermissionsToSystem(permissions);
        } else if (!rationaleAccepted.get()) {
            PermissionRationaleToken permissionToken = new PermissionRationaleToken(this);
            listener.onPermissionRationaleShouldBeShown(shouldShowRequestRationalePermissions, permissionToken);
        }
    }

    private void updatePermissionsAsGranted(Collection<String> permissions) {
        for (String permission : permissions) {
            PermissionGrantedResponse response = PermissionGrantedResponse.from(permission);
            multiplePermissionsReport.addGrantedPermissionResponse(response);
            DexterLogUtil.info("dexterLogUtil", "18-7");
        }
        DexterLogUtil.info("dexterLogUtil", "18-6");
        onPermissionsChecked(permissions);
    }

    private void updatePermissionsAsDenied(Collection<String> permissions) {
        for (String permission : permissions) {
            PermissionDeniedResponse response =
                    PermissionDeniedResponse.from(
                            permission, harmonyPermissionService.isPermissionPermanentlyDenied(ability, permission));
            multiplePermissionsReport.addDeniedPermissionResponse(response);
            DexterLogUtil.info("dexterLogUtil", "18-8");
        }
        DexterLogUtil.info("dexterLogUtil", "18-9");
        onPermissionsChecked(permissions);
    }

    private void onPermissionsChecked(Collection<String> permissions) {
        if (pendingPermissions.isEmpty()) {
            return;
        }
        DexterLogUtil.info("dexterLogUtil", "26-1");
        synchronized (PENDINGPERMISSIONSMUTEX) {
            pendingPermissions.removeAll(permissions);
            if (pendingPermissions.isEmpty()) {
                DexterLogUtil.info("dexterLogUtil", "26-2");
                // Just in case heck to avoid NPE if the OS destroys the activity before
                DexterLogUtil.info("dexterLogUtil", "26-3");
                isRequestingPermission.set(false);
                rationaleAccepted.set(false);
                isShowingNativeDialog.set(false);
                MultiplePermissionsListener currentListener = listener;

                listener = empty_listener;
                DexterLogUtil.info("dexterLogUtil", "26-3-1:currentListener:" + currentListener.toString());
                DexterLogUtil.info("dexterLogUtil", "26-3-1:empty_listener:" + empty_listener.toString());

                DexterLogUtil.info(
                        "dexterLogUtil", "26-3-1:multiplePermissionsReport:" + multiplePermissionsReport.toString());

                currentListener.onPermissionsChecked(multiplePermissionsReport);
                DexterLogUtil.info("dexterLogUtil", "26-4");
            }
        }
    }

    private void checkNoDexterRequestOngoing() {
        if (isRequestingPermission.getAndSet(true)) {
            throw new DexterException("Only one Dexter request at a time is allowed", DexterError.REQUEST_ONGOING);
        }
    }

    private void checkRequestSomePermission(Collection<String> permissions) {
        if (permissions.isEmpty()) {
            throw new DexterException(
                    "Dexter has to be called with at least one permission", DexterError.NO_PERMISSIONS_REQUESTED);
        }
    }

    private void checkSinglePermission(PermissionListener listener, String permission, Thread thread) {
        MultiplePermissionsListener adapter = new MultiplePermissionsListenerToPermissionListenerAdapter(listener);
        DexterLogUtil.info("dexterLogUtil", "listener-9-4");
        checkMultiplePermissions(adapter, Collections.singleton(permission), thread);
    }

    private void checkMultiplePermissions(
            final MultiplePermissionsListener listener, final Collection<String> permissions, Thread thread) {
        DexterLogUtil.info("dexterLogUtil", "6");
        checkNoDexterRequestOngoing();
        DexterLogUtil.info("dexterLogUtil", "7");
        checkRequestSomePermission(permissions);
        DexterLogUtil.info("dexterLogUtil", "8");
        if (abilityContext.get() == null) {
            return;
        }

        if (ability != null && ability.isTerminating()) {
            DexterLogUtil.info("dexterLogUtil", "9");
            onActivityDestroyed(ability);
        }

        pendingPermissions.clear();
        pendingPermissions.addAll(permissions);
        multiplePermissionsReport.clear();
        DexterLogUtil.info("dexterLogUtil", "10");
        this.listener = new MultiplePermissionListenerThreadDecorator(listener, thread);
        DexterLogUtil.info("dexterLogUtil", "10-1-permissions:" + permissions.toString());
        if (isEveryPermissionGranted(permissions, abilityContext.get())) {
            DexterLogUtil.info("dexterLogUtil", "11");
            thread.execute(
                    new Runnable() {
                        @Override
                        public void run() {
                            DexterLogUtil.info("dexterLogUtil", "12");
                            MultiplePermissionsReport report = new MultiplePermissionsReport();
                            for (String permission : permissions) {
                                report.addGrantedPermissionResponse(PermissionGrantedResponse.from(permission));
                            }
                            DexterLogUtil.info("dexterLogUtil", "13");
                            isRequestingPermission.set(false);
                            listener.onPermissionsChecked(report);
                            DexterInstance.this.listener = empty_listener;
                            DexterLogUtil.info("dexterLogUtil", "14");
                        }
                    });
        } else {
            String bundleName = abilityContext.get().getBundleName();
            DexterLogUtil.info("dexterLogUtil", "15" + bundleName);
            startTransparentActivityIfNeeded(bundleName);
        }
        thread.loop();
    }

    private boolean isEveryPermissionGranted(Collection<String> permissions, Context context) {
        for (String permission : permissions) {
            int permissionState = harmonyPermissionService.checkSelfPermission(context, permission);
            if (permissionState != IBundleManager.PERMISSION_GRANTED) {
                DexterLogUtil.info("dexterLogUtil", "16");
                return false;
            }
        }
        DexterLogUtil.info("dexterLogUtil", "17");
        return true;
    }

    private final class PermissionStates {
        private final Collection<String> deniedPermissions = new LinkedList<>();
        private final Collection<String> impossibleToGrantPermissions = new LinkedList<>();
        private final Collection<String> grantedPermissions = new LinkedList<>();

        private void addDeniedPermission(String permission) {
            deniedPermissions.add(permission);
        }

        private void addImpossibleToGrantPermission(String permission) {
            impossibleToGrantPermissions.add(permission);
        }

        private void addGrantedPermission(String permission) {
            grantedPermissions.add(permission);
        }

        private Collection<String> getDeniedPermissions() {
            return deniedPermissions;
        }

        private Collection<String> getGrantedPermissions() {
            return grantedPermissions;
        }

        /**
         * 获取可能授权的权限
         *
         * @return Collection
         */
        public Collection<String> getImpossibleToGrantPermissions() {
            return impossibleToGrantPermissions;
        }
    }
}
